package business;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;


import entidad.Articulo;
import entidad.ArticuloClasificado;
import entidad.ArticuloPreClasificado;
import entidad.Estadistica;
import entidad.Word;


public class ClasificadorNaiveBayes implements ClasificadorInterfaz {	


	public void aprender(Collection<ArticuloPreClasificado> articulos) {
		
		//contiene la cantidad de veces que una palabra aparece en un articulo interesante
		HashMap<String, Integer> palabrasEnInteresante = new HashMap<String, Integer>();
		
		//contiene la cantidad de veces que una palabra aparece en un mail que no es interesante
		HashMap<String, Integer> palabrasEnNoInteresante = new HashMap<String, Integer>();
		
		//totales de articulos interesantes y no interesantes
		int totalArticulosInteresantes = 0;
		int totalArticulosNoInteresantes = 0;
		
		//por cada articulo
		for (ArticuloPreClasificado articuloClasificado : articulos) {
			HashSet<String> wordProcessed = new HashSet<String>();
			
			//por cada palabra del articulo
			for (String word : articuloClasificado.getNonStopWords()) {
			    word = word.toLowerCase();
			    
				//se procesa un sola vez una palabra dentro de un articulo
				if (!wordProcessed.contains(word)){
					wordProcessed.add(word);
					
					//si es interesante
					if (articuloClasificado.isInteresante()){
						if (palabrasEnInteresante.containsKey(word)){
							palabrasEnInteresante.put(word, palabrasEnInteresante.get(word)+1);
						} else {
							palabrasEnInteresante.put(word, new Integer(1));
						}
					} else {
						//si no es interesante
						if (palabrasEnNoInteresante.containsKey(word)){
							palabrasEnNoInteresante.put(word, palabrasEnNoInteresante.get(word)+1);
						} else {
							palabrasEnNoInteresante.put(word, new Integer(1));
						}
					}
				}
			}
			//se procesaron todas las palabras del articulo
			if (articuloClasificado.isInteresante()){
				totalArticulosInteresantes++;
			} else {
				totalArticulosNoInteresantes++;
			}
		}
		
		//guardar las probabilidades inciales.
		registrarProbabilidades(palabrasEnInteresante, palabrasEnNoInteresante, totalArticulosInteresantes, totalArticulosNoInteresantes);
	}
	
	/**
	 * Registra las probabilidades de cada una de las palabras encontradas en los articulos preclasificados.
	 * @param palabrasEnInteresante matchea con las palabras encontradas en un articulo y la cantidad de veces que fueron encontradas
	 * @param palabrasEnNoInteresantes matchea con las palabras encontradas en un mail no interesantes y la cantidad de veces que fueron encontradas
	 * @param totalArticulosInteresates: cantidad total de articulos interesantes
	 * @param totalArticulosNoInteresantes: cantidad total de articulos no interesantes
	 */
	private void registrarProbabilidades(HashMap<String, Integer> palabrasEnInteresante, HashMap<String, Integer> palabrasEnNoInteresantes, 
			int totalArticulosInteresates, int totalArticulosNoInteresantes){
		
		//borrar registros previos
		Word.deleteAll();
		Estadistica.deleteAll();
		
		//recorro cada palabra que se encontro en un articulo interesante
        HashMap<String, Word> allWords = new HashMap<String, Word>();	   
        
		for (Entry<String, Integer> entry: palabrasEnInteresante.entrySet()) {
			Word word = new Word();
			word.setId(entry.getKey());
			word.setProbabilidadInteresante(new BigDecimal(entry.getValue().intValue()).divide(new BigDecimal(totalArticulosInteresates), 3, BigDecimal.ROUND_DOWN));
			word.setProbabilidadNoInteresante(new BigDecimal("0"));
			allWords.put(word.getId(), word);
		}
		
		//lo mismo pero para cada palabra que se encontro en un mail que no es de spam
		for (Entry<String, Integer> entry: palabrasEnNoInteresantes.entrySet()){
		    Word word = new Word();
            // busco si ya existe con la probabilidad de spam
            if (allWords.containsKey(entry.getKey())) {
                word.setProbabilidadInteresante(allWords.get(entry.getKey()).getProbabilidadInteresante());
            } else {                
                word.setProbabilidadInteresante(new BigDecimal("0"));
            }

            word.setId(entry.getKey());
            word.setProbabilidadNoInteresante(new BigDecimal(entry.getValue().intValue()).divide(new BigDecimal(totalArticulosNoInteresantes), 3, BigDecimal.ROUND_DOWN));
            allWords.put(word.getId(), word);
		}
		
		Word.saveAll(allWords.values());
		
		//estadisticas
		Estadistica estadistica = new Estadistica();
		estadistica.setTotalArticulosNoInteresantes(totalArticulosNoInteresantes);
		estadistica.setTotalArticulosInteresantes(totalArticulosInteresates);
		estadistica.save();
		
	}

	/**
	 * 
	 * {@inheritDoc}
	 */
	public boolean isEntrenado() {
		if (Estadistica.find() != null){
			return true;
		}
		
		return false;
	}

	/**
	 * 
	 * {@inheritDoc}
	 */
	public Collection<ArticuloClasificado> clasificar(Collection<Articulo> articulos) throws Exception{		
		Estadistica estadistica = Estadistica.find();
		if (estadistica == null){
			throw new Exception("No se entreno al clasificador");
		}
		
		Collection<ArticuloClasificado> articulosClasificados = new ArrayList<ArticuloClasificado>();
		for (Articulo articulo : articulos) {
			
			//determinar la probabilidad de que las palabras del articulo esten en un intersante y en un no interesante
			BigDecimal probaPalabrasEnInteresante = new BigDecimal("0");
			BigDecimal probaPalabrasEnNoInteresante = new BigDecimal("0");
			for (String wordInMail : articulo.getNonStopWords()) {
				Word word = Word.find(wordInMail);				
				if (word != null){					
					probaPalabrasEnNoInteresante = probaPalabrasEnNoInteresante.add(word.getProbabilidadNoInteresante());
					probaPalabrasEnInteresante = probaPalabrasEnInteresante.add(word.getProbabilidadInteresante());					
				}								
			}
			
			/** Hago los calculos para aplicar el teorema de Bayes **/
			//cantidad total de mails			
			int totalMails = estadistica.getTotalArticulosNoInteresantes() + estadistica.getTotalArticulosInteresantes();
			//falta dividir por la cantidad total de mails
			probaPalabrasEnNoInteresante = probaPalabrasEnNoInteresante.divide(new BigDecimal(totalMails), 3, BigDecimal.ROUND_DOWN);
			probaPalabrasEnInteresante = probaPalabrasEnInteresante.divide(new BigDecimal(totalMails), 3, BigDecimal.ROUND_DOWN);			
			//probabilidad de que cualquier mail sea spam. P(spam)
			BigDecimal probaCualquierArticuloInteresante =  new BigDecimal(estadistica.getTotalArticulosInteresantes()).divide(new BigDecimal(totalMails), 3, BigDecimal.ROUND_DOWN);
			//probabilidad de que cualquier mail NO sea spam. P(no spam)
			BigDecimal probaCualquierArticuloNoInteresante = new  BigDecimal(estadistica.getTotalArticulosNoInteresantes()).divide(new BigDecimal(totalMails), 3, BigDecimal.ROUND_DOWN);
			//probabilidad que las palabras de este mail, aparezcan en cualquier mail. P(palabras)			
			BigDecimal probaPalabrasCualquierArticulo = probaPalabrasEnNoInteresante.add(probaPalabrasEnInteresante);
			//probabilidad de spam = P(palabras/spam) P(spam) / P(palabras)
			BigDecimal probaEsInteresante = (probaPalabrasEnInteresante.multiply(probaCualquierArticuloInteresante)).divide(probaPalabrasCualquierArticulo, 3, BigDecimal.ROUND_DOWN);
			//probabilidad de NO spam = P(palabras/no spam) P(no spam) / p(palabras)
			BigDecimal probaNoEsInteresante = (probaPalabrasEnNoInteresante.multiply(probaCualquierArticuloNoInteresante)).divide(probaPalabrasCualquierArticulo, 3, BigDecimal.ROUND_DOWN);
			
			ArticuloClasificado articuloClasificado = new ArticuloClasificado();
			articuloClasificado.setArchivo(articulo.getArchivo());
			articuloClasificado.setAsunto(articulo.getAsunto());
			articuloClasificado.setBody(articulo.getBody());
			articuloClasificado.setProbabilidadNoInteresante(probaNoEsInteresante);
			articuloClasificado.setProbabilidadInteresante(probaEsInteresante);
			articuloClasificado.setInteresante(probaEsInteresante.compareTo(probaNoEsInteresante) == 1);
			articulosClasificados.add(articuloClasificado);
		}
		
		return articulosClasificados;
	}
	
}
